//
// Serializer.cs
//
// Authors:
//   Alan McGovern alan.mcgovern@gmail.com
//
// Copyright (C) 2007 Alan McGovern
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of Rthe Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//


using System;
using System.Threading;
using System.Globalization;
using System.Reflection;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Xml;
using System.Text;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using MonoDevelop.XamlDesigner.Model;
using System.Windows.Documents;


namespace MonoDevelop.XamlDesigner.Serialization
{
    internal class Serializer
    {
		private FrameworkElement root;
        private static Dictionary<Type, DependencyObject> defaultValues;
		
        internal Serializer()
        {
            defaultValues = new Dictionary<Type, DependencyObject>();
        }

        internal static string CleanName(string fieldName)
        {
            // The name of all DependencyProperties ends in "Property", i.e.
            // NameProperty, FillProperty. this method removes "Property" from
            // the end of each name so i serialise the correct name into XAML
            return fieldName.Substring(0, fieldName.Length - 8);
        }
        
        private void SerialiseCollection(PropertyData propertyData, object value, XmlWriter writer)
        {
			if (value is PointCollection)
				return; 
			
            IEnumerable collection = (IEnumerable)value;
			
            //FIXME: Nasty hack as the collections don't have a common interface
			IEnumerator enumerator;
		 	try {
            	enumerator = collection.GetEnumerator();
			}
			catch (NotImplementedException e) {
				System.Diagnostics.Debug.WriteLine("Something is not implemented in moonlight");
				return;
			}
            enumerator.Reset();
			if(!enumerator.MoveNext())
				return;

			// We need to write the full qualified name for correct serializing, meaning:
			// <TransformGroup.Children><TransformCollection>..... /> <TransformGroup.Children/>
			//string name = null;
			//if(propertyData.Attached)
			//	name = propertyData.DeclaringType.Name;
			//else
			//	name = propertyData.BaseType.Name;
			//
			//name += '.' + propertyData.ShortName;
            //
			//writer.WriteStartElement(name);
            if (!(value is UIElementCollection) && !(value is InlineCollection))
				writer.WriteStartElement(value.GetType().Name);
			
            foreach(DependencyObject o in collection)
                Serialize(o, writer);
            
			if (!(value is UIElementCollection) && !(value is InlineCollection))
				writer.WriteEndElement();
            
			//writer.WriteEndElement();
        }
        
        public string Serialize(FrameworkElement root, string xClass)
        {
			// This is used to prevent bad serializations on other cultures
			// For example in spanish, doubles would be serialized like
			// "1,0" instead of "1.0"
			CultureInfo culture = Thread.CurrentThread.CurrentCulture;
			Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
			this.root = root;
			
            StringBuilder sb = new StringBuilder();
            XmlWriterSettings settings = new XmlWriterSettings(); 
            settings.OmitXmlDeclaration=true;
            settings.Indent = true;
            settings.IndentChars = "    ";
            settings.NewLineOnAttributes = false;
            using(XmlWriter writer = XmlWriter.Create(sb, settings))
                Serialize(root, writer);
            
            //this is a quick hack because xaml widget do not support x:class!
            XmlDocument doc = new XmlDocument ();
            doc.LoadXml(sb.ToString());
            XmlElement rootelt = doc.DocumentElement;
            
            XmlAttribute att = doc.CreateAttribute("x", "class","http://schemas.microsoft.com/winfx/2006/xaml/presentation");
            att.Value = xClass;
            rootelt.Attributes.Append(att);
            
            //att = doc.CreateAttribute("xmlns");
            //att.Value = "http://schemas.microsoft.com/winfx/2006/xaml/presentation";
            //rootelt.Attributes.Append(att);
            
            return doc.InnerXml;
            Thread.CurrentThread.CurrentCulture = culture;
        }
        
        private void Serialize(DependencyObject item, XmlWriter writer)
        {
			System.Diagnostics.Debug.WriteLine(item);
			System.Diagnostics.Debug.Indent();
			if (item is IHandle || item is IFrame)
				return;
			
            Type baseType = item.GetType();
			
//			if(string.IsNullOrEmpty(item.Name)) {
//				item.SetValue(DependencyObject.NameProperty, NameGenerator.GetName(this.canvas, item));
//				System.Console.WriteLine("#### "+ baseType.Name);
//				System.Console.WriteLine(item.Name);
//			}
            
			// Gets all the dependency properties for this item type
            // and any relevant attached properties.
            List<PropertyData> fields = ReflectionHelper.GetProperties(item);
            
            // First we need to write all the dependency properties whose value is *not*
            // a dependency object as attributes. Every DependencyProperty whose value
            // is a dependency object must be written as an element.
            writer.WriteStartElement(baseType.Name);
			System.Diagnostics.Debug.WriteLine("properties:");
			System.Diagnostics.Debug.Indent();
            foreach(PropertyData prop in fields)
            {
                System.Diagnostics.Debug.WriteLine(prop.ShortName);
				DependencyProperty dependencyProperty = prop.Property;
				object value = null;
			 	try {
                		value = item.GetValue(dependencyProperty);
				}
				catch {
					System.Diagnostics.Debug.WriteLine(string.Format("Error getting value: {0}.{1} on {2}", prop.DeclaringType, prop.ShortName, item));
				}
				
				if (value is PointCollection) {
					PointCollection collection = value as PointCollection;
					string[] points = new string[collection.Count];
					for (int i=0; i<collection.Count; i++) {
						Point p = collection[i];
						points[i] = string.Format("{0},{1}", p.X, p.Y);
					}
					writer.WriteAttributeString(prop.ShortName, string.Join(" ", points));
					continue;
				}
                if (value is SolidColorBrush && (value != null) && item.ReadLocalValue(dependencyProperty) != DependencyProperty.UnsetValue)
                {
                    string name = prop.ShortName;
					if(prop.Attached)
                        name = prop.DeclaringType.Name + "." + name;
                    writer.WriteAttributeString(name, ((SolidColorBrush)value).Color.ToString());
                    continue;
                }
				
                if(!(value is DependencyObject) && (value != null) && item.ReadLocalValue(dependencyProperty) != DependencyProperty.UnsetValue)
                {
                    string name = prop.ShortName;
					if(prop.Attached)
                        name = prop.DeclaringType.Name + "." + name;
                    writer.WriteAttributeString(name, value.ToString());
                }
            }
			System.Diagnostics.Debug.Unindent();
            
            // After we write out all the attributes we can then write out
            // the child elements.
            foreach(PropertyData prop in fields)
            {
                object dependencyValue = prop.Property;
                object value = null;
				
			 	try {
					value = item.GetValue((DependencyProperty)dependencyValue);
				}
				catch {
					System.Diagnostics.Debug.WriteLine(string.Format("Error getting value: {0}.{1} on {2}", prop.DeclaringType, prop.ShortName, item));
				}

				// We've already serialised non-dependency objects, so now serialise
				// only the dependency objects
				if(!(value is DependencyObject))
					continue;
                
				if (value is SolidColorBrush)
                    continue;
                
                if (!prop.IsContent) {
                    string name = null;
                    // We need to write the full qualified name for correct serializing, meaning:
					// <TransformGroup.Children><TransformCollection>..... /> <TransformGroup.Children/>
					if(prop.Attached)
						name = prop.DeclaringType.Name;
					else
						name = prop.BaseType.Name;
			
					name += '.' + prop.ShortName;
                    
                	writer.WriteStartElement(name);
                }
                    
                if (value is IEnumerable) {
					SerialiseCollection(prop, value, writer);
				}//not a default value
				else if(item.ReadLocalValue(prop.Property) != DependencyProperty.UnsetValue)
                {
                    Serialize((DependencyObject)value, writer);
                }
                
                if (!prop.IsContent) {
                	writer.WriteEndElement();
                }}
     
            writer.WriteEndElement();
			System.Diagnostics.Debug.Unindent();
        }
		
		public static DependencyObject Clone(FrameworkElement root, DependencyObject item)
        {
            System.Diagnostics.Debug.WriteLine("Starting Cloning:");
			System.Diagnostics.Debug.Indent();
			Type type = item.GetType();
            DependencyObject newObject = (DependencyObject) Activator.CreateInstance(type);
			
            List<PropertyData> fields = ReflectionHelper.GetProperties(item);
			
			foreach(PropertyData prop in fields) {
				System.Diagnostics.Debug.WriteLine("* Property: " + prop.ShortName);
				System.Diagnostics.Debug.Indent();
				
                DependencyProperty dependencyProperty = prop.Property;
				
                object value = null;
				
				try {
					value = item.GetValue(dependencyProperty);
				}
				catch {
					System.Diagnostics.Debug.WriteLine(string.Format("Error getting value: {0}.{1} on {2}", prop.DeclaringType, prop.ShortName, item));
				}
                //not a default value
				if(item.ReadLocalValue(dependencyProperty) != null)
                {
                    object newValue = null;

					if (value is PointCollection) {
						PointCollection newPoints = new PointCollection();
						foreach (Point p in (value as PointCollection) )
							newPoints.Add(p);
						newValue = newPoints;
					}
					
					else if(value is IEnumerable && !(value is string)) {
						Type collectionType = value.GetType();
						System.Diagnostics.Debug.WriteLine("* IEnumerable: " + collectionType.ToString());
						System.Diagnostics.Debug.Indent();
						newValue = Activator.CreateInstance(collectionType);
						try {
							foreach (DependencyObject o in value as IEnumerable) {
								DependencyObject member = Clone(root, o);
								newValue.GetType().GetMethod("Add").Invoke(newValue, new object[] { member });
							}
						}
						catch (NotImplementedException e) {
							System.Diagnostics.Debug.WriteLine("Something is not implemented in moonlight");
						}
						System.Diagnostics.Debug.Unindent();
					}
					
					else if (value is DependencyObject) {
						newValue = Clone(root, value as DependencyObject);
					}
					
					else {
						newValue = value;
					}
					
					try {
						newObject.SetValue(dependencyProperty, newValue);
					}
					catch (Exception e){
						System.Diagnostics.Debug.WriteLine(string.Format("*** couldn't clone {0}: {1}", prop.ShortName, e.Message));
					}
                }
				
				System.Diagnostics.Debug.Unindent();
            }
			System.Diagnostics.Debug.Unindent();
			
			if (!string.IsNullOrEmpty((string)item.GetValue(FrameworkElement.NameProperty)))
				newObject.SetValue(FrameworkElement.NameProperty, NameGenerator.GetName(root, newObject));

			return newObject;
		}
    }
}
